/**
 * GrimaceGamelet.java
 *
 * @author Created by Omnicore CodeGuide

 * This class takes care of the framerate syncing,
 *  Double buffering and hardware acceleration when extended.
 */
package ronin.graphics;

import java.applet.*;
import java.awt.geom.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import ronin.graphics.*;
import ronin.actor2d.Actor2D;
import ronin.util.*;
import ronin.scene.QuadScene;
import com.dnsalias.java.timer.AdvancedTimer;



// TODO: Auto-generated Javadoc
/**
 * The Class GrimaceGamelet.
 */
public class GrimaceGamelet extends Applet implements Runnable, KeyListener,
	MouseListener {

    // for Double Buffering
    /** The offscreen. */
    public static BufferedGraphics offscreen;

    /** The offscreen image as a H/W accelerated Volatile image. */
    public static VolatileImage xoffscreen;

    public static GrimaceGamelet mainApplet;
    
    /** 
     * The frames between sleep time check; is used as a controll value to 
     * make sue that the system sleep time is recalculated every so many frames. 
     * once profiling is done, we will know if we need this anymore, for now its 
     * set to check it every 30 frames roughly, but this can be adjusted with 
     * setSleeptimeCheckValue(int amtFramesBetweenSleeptimeChecks)  Statically 
     * set to 30 frames between each check. 
     */
    protected static int framesBetweenSleepTimeCheck = 30; // defaults to 30

    // Manages the state of the keyboard so that the user can poll for key
    // events.
    /** The ronin_keys. */
    protected static int ronin_keys;

    // up and down, used to test key states.
    /** The Constant KEY_UP. */
    public final static int KEY_UP = 0;

    /** The Constant KEY_DOWN. */
    public final static int KEY_DOWN = 1;

    // RoninDefault keyset.
    /** The Constant RK_LEFT. */
    public final static int RK_LEFT = 1;

    /** The Constant RK_RIGHT. */
    public final static int RK_RIGHT = 2;

    /** The Constant RK_UP. */
    public final static int RK_UP = 4;

    /** The Constant RK_DOWN. */
    public final static int RK_DOWN = 8;

    /** The Constant RK_CTRL. */
    public final static int RK_CTRL = 16;

    /** The Constant RK_ALT. */
    public final static int RK_ALT = 32;

    /** The Constant RK_SPACE. */
    public final static int RK_SPACE = 64;

    /** The Constant RK_ENTER. */
    public final static int RK_ENTER = 128;

    /** The Constant RK_S. */
    public final static int RK_S = 256;

    /** The Constant RK_D. */
    public final static int RK_D = 512;

    /** The Constant RK_F. */
    public final static int RK_F = 1024;

    /** The Constant RK_G. */
    public final static int RK_G = 2048;

    /** The Constant RK_ESC. */
    public final static int RK_ESC = 4096; // BY DEFAULT THIS SHOULD STOP THE
    // APPLICATION.

    /** The g2d. */
    protected static Graphics2D g2d;

    // for animation
    /** The animation. */
    private Thread animation;

    /** The gamelet started. */
    public boolean gameletStarted = false;

    // for hardware acceleration
    /** The hw acceleration. */
    public boolean hwAcceleration;

    // frame rate synching
    /** The framerate desired. */
    protected long framerate;

    // game time in frames
    /** The ticks. */
    protected long ticks;

    // rendering hints
    /** The default rendering hint. */
    private RenderingHints defaultRenderingHint;

    // this is anagolous to the reportedFramerate, it is the
    // amount of frames
    /** The ticks per second. */
    public long ticksPerSecond;
    
    /** Advanced timer */
    public AdvancedTimer timer;

    // used to offset the y start position within the window because of the
    // MenuBar.
    /** The paint offset. */
    protected int paintOffset = 20;

    // Counters & Temp Vars
    
    /** The total elapsed time. */
    public long frameStart = 0, 
    			 frameCount = 0, 
    			 elapsedTime = 0,
	    		 totalElapsedTime = 0;

    /** The app height. */
    protected int appWidth, appHeight;

    /** The reported framerate. */
    double reportedFramerate = 0;

    /** The game time in seconds, minutes and hours. // not yet implemented */
    int gameTimeSeconds, gameTimeMinutes, gameTimeHours;

    /** The button3 pressed. */
    public boolean mouseClicked, button1Pressed, button2Pressed,
	    button3Pressed;

    /** The mouse point. */
    public Vector2D mousePoint;

    /** The mouse click timer. */
    public int mouseClickTimer;
    
    /** a directory of all entities in the Gamelet*/
  	private EntityManager directory;

	private QuadScene sceneManager;
    /**
     * Instantiates a new Grimace gamelet.
     * 
     * @param width the width
     * @param height the height
     */
    public GrimaceGamelet(int width, int height) {
		super();

		appWidth = width;
		appHeight = height;
		setMainApplet(this);
		hwAcceleration = false;
    }

    /**
     * Instantiates a new grimace gamelet.
     * 
     * @param width the width
     * @param height the height
     * @param hardwareAccel the hardware accel
     */
    public GrimaceGamelet(int width, int height, boolean hardwareAccel) {
		super();
		setMainApplet(this);
		appWidth = width;
		appHeight = height;
		hwAcceleration = hardwareAccel;
    }
	
	private void setMainApplet(GrimaceGamelet applet){
		mainApplet = applet;
	}
	
	public static GrimaceGamelet getMainApplet(){ return mainApplet; }
    /**
     * Creates the offscreen image.
     */
    protected void createOffscreenImage() {
		if (hwAcceleration) {
		    // create the offscreen buffer
		    out("Volatile Image created!");
		    out("Hardware Acceration is in effect!");
		    xoffscreen = getGraphicsConfiguration()
			    .createCompatibleVolatileImage(getSize().width,
				    getSize().height);
		} else {
		    // create the offscreen buffer -
		    out("Software back buffer created!");
		    offscreen = new BufferedGraphics(this);
		    out("BufferedGraphics started.");
		}
	}

    /**
     * Returns the exact tick time value generated each frame by the Gamelet.
     * 
     * @return the elapsed time
     */
    public long getElapsedTime() {
		return elapsedTime;
    }

    /**
     * Gets the time elapsed in milli seconds.
     * 
     * @return the time elapsed in milli seconds
     */
    public long getTimeElapsedInMilliSeconds() {
		return (totalElapsedTime * 1000) / ticksPerSecond;
    }

    /**
     * Gets the time elapsed in seconds.
     * 
     * @return the time elapsed in seconds
     */
    public long getTimeElapsedInSeconds() {
		return totalElapsedTime / ticksPerSecond;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.applet.Applet#init()
     */
	 public void init() {
		super.init();
		ticks = 0;
	
		resize(appWidth, appHeight + paintOffset);
	
		// by default hwAcceleration is on
		hwAcceleration = true;
		createOffscreenImage();
	
		// setup
		defaultRenderingHint = new RenderingHints(RenderingHints.KEY_RENDERING,
			RenderingHints.VALUE_RENDER_SPEED);
		defaultRenderingHint.put(RenderingHints.KEY_COLOR_RENDERING,
			RenderingHints.VALUE_COLOR_RENDER_SPEED);
		defaultRenderingHint.put(RenderingHints.KEY_ANTIALIASING,
			RenderingHints.VALUE_ANTIALIAS_OFF);
	
		// setup animation and framerate
		animation = new Thread(this);
		out("Animation Thread initialized.");
	
		framerate = 60;
		out("Framerate set at " + framerate + "fps by default.");
	
		// used for mouse pointer location
		mousePoint = new Vector2D.Integer();
	
		setupGame();
	
		addKeyListener(this);
		addMouseListener(this);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
     */
    public void keyPressed(KeyEvent e) {
		int kcode = e.getKeyCode();
	
		if (kcode == KeyEvent.VK_UP)
		    ronin_keys |= RK_UP;
		if (kcode == KeyEvent.VK_DOWN)
		    ronin_keys |= RK_DOWN;
	
		if (kcode == KeyEvent.VK_LEFT)
		    ronin_keys |= RK_LEFT;
		if (kcode == KeyEvent.VK_RIGHT)
		    ronin_keys |= RK_RIGHT;
	
		if (kcode == KeyEvent.VK_S)
		    ronin_keys |= RK_S;
		if (kcode == KeyEvent.VK_D)
		    ronin_keys |= RK_D;
	
		if (kcode == KeyEvent.VK_F)
		    ronin_keys |= RK_F;
		if (kcode == KeyEvent.VK_ENTER)
		    ronin_keys |= RK_ENTER;
		if (kcode == KeyEvent.VK_SPACE)
		    ronin_keys |= RK_SPACE;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
     */
    public void keyReleased(KeyEvent e) {
		int kcode = e.getKeyCode();
	
		if (kcode == KeyEvent.VK_LEFT)
		    ronin_keys &= ~RK_LEFT;
		if (kcode == KeyEvent.VK_RIGHT)
		    ronin_keys &= ~RK_RIGHT;
		if (kcode == KeyEvent.VK_UP)
		    ronin_keys &= ~RK_UP;
		if (kcode == KeyEvent.VK_DOWN)
		    ronin_keys &= ~RK_DOWN;
		if (kcode == KeyEvent.VK_SPACE)
		    ronin_keys &= ~RK_SPACE;
		if (kcode == KeyEvent.VK_ENTER)
		    ronin_keys &= ~RK_ENTER;
		if (kcode == KeyEvent.VK_S)
		    ronin_keys &= ~RK_S;
		if (kcode == KeyEvent.VK_D)
		    ronin_keys &= ~RK_D;
		if (kcode == KeyEvent.VK_F)
		    ronin_keys &= ~RK_F;
		// if(kcode == KeyEvent.VK_G) ronin_keys &= ~RK_G;

    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
     */
    public void keyTyped(KeyEvent e) {
    }

    /**
     * For now this code does nothing, but any time the app is closed, this
     * should be called.
     */
    protected void killGame() {

    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
     */
    public void mouseClicked(MouseEvent e) {
	/*
	 * mouseClicked = true; /// mouseClickTimer = 2;
	 * mousePoint.setX(e.getX()); mousePoint.setY(e.getY());
	 * 
	 * if((e.getModifiers() & InputEvent.BUTTON3_MASK) ==
	 * InputEvent.BUTTON3_MASK) { button3Pressed = true; return; }
	 * 
	 * if((e.getModifiers() & InputEvent.BUTTON2_MASK) ==
	 * InputEvent.BUTTON2_MASK) { button2Pressed = true; return; }
	 * 
	 * if((e.getModifiers() & InputEvent.BUTTON1_MASK) ==
	 * InputEvent.BUTTON1_MASK) { button1Pressed = true; return; }
	 */
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
     */
    public void mouseEntered(MouseEvent e) {

    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
     */
    public void mouseExited(MouseEvent e) {

    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
     */
    public void mousePressed(MouseEvent e) {
		mouseClicked = true;
		mousePoint.setX(e.getX());
		mousePoint.setY(e.getY());
	
		if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) {
		    button3Pressed = true;
		    return;
		}
	
		if ((e.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK) {
		    button2Pressed = true;
		    return;
		}
	
		if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
		    button1Pressed = true;
		    return;
		}
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
     */
    public void mouseReleased(MouseEvent e) {
		mouseClicked = false;
	
		if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) {
		    button3Pressed = false;
		    return;
		}
	
		if ((e.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK) {
		    button2Pressed = false;
		    return;
		}
	
		if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
		    button1Pressed = false;
		    return;
		}
    }

    /**
     * Outputs a given string to the the console.
     * 
     * @param string to output.
     */
    protected void out(String s) {
		System.out.println("Ronin.GrimaceGamelet::" + s);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.Container#paint(java.awt.Graphics)
     */
    public void paint(Graphics g) {
		if (!gameletStarted)
		    return;
	
		try {
			// Handle Messages before running game code so actors have the most up to date info.
			MessageDispatcher.getInstance().dispatchDelayedMessages();
		    runGame();
		} catch (Exception e) {
		}
	
		// if accelerated
		if (hwAcceleration) {
		    int returnCode;
		    VolatileImage volatileImage = (VolatileImage) xoffscreen;
	
		    do {
			returnCode = volatileImage.validate(getGraphicsConfiguration());
	
			if (returnCode == VolatileImage.IMAGE_INCOMPATIBLE) {
			    createOffscreenImage();
			}
	
			else {
			    g2d = (Graphics2D) volatileImage.createGraphics();
			    g2d.setRenderingHints(defaultRenderingHint);
			    paintScene(g2d);
	
			    g.drawImage(volatileImage, 0, 0, this);
			}
	
		    } while (volatileImage.contentsLost());
	
		    volatileImage = null;
		} else {
		    // get G2D and paint scene
		    g2d = (Graphics2D) offscreen.getValidGraphics();
		    paintScene(g2d);
		    g.drawImage(offscreen.getBuffer(), 0, paintOffset, this);
		    out("Say it loud!");
		}
    } // paint

    /**
     * Paint scene.
     * 
     * @param g the g
     */
    protected void paintScene(Graphics2D g) {

    }
    
    /** Registers an Actor with the entityManager.*/
    public void registerEntity(Actor2D entity){
    	EntityManager.getInstance().register(entity);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Runnable#run()
     */
    public void run() {
		timer = new AdvancedTimer();
		
		// this is done after timer to make sure that we have timer created, 
		// message dispatch will use it in newMessage.
		MessageDispatcher.getInstance().registerGamelet(this);
		
		// represents the Timer Clook speed.
		ticksPerSecond = timer.getTicksPerSecond();
		long sleepTime = ticksPerSecond / framerate;
		ticks = 0;
		frameCount = 0;
		frameStart = 0;
		totalElapsedTime = 0;
		timer.start();
		totalElapsedTime = timer.getClockTicks();
		long currTotElapsedTime;
	
		while (true) {
		    if (!gameletStarted) {
			out("Dude, the game hasn't been started yet!");
			return;
		    }
		    // every 10 frames lets check the ticks per second value
		    // and reset it just in case things slow down and we have
		    // less framss being drawn, we may want to adjust our sleep
		    // time accourdingly. Is a candidate for a performance hit.
		    if (frameCount % framesBetweenSleepTimeCheck == 0) {
			sleepTime = ticksPerSecond / framerate;
			this.out("GameTimeInSec = " + (totalElapsedTime)
				/ ticksPerSecond);
		    }
		    repaint();
		    timer.sleepUntil(ticks + sleepTime);
	
		    // setup main scene variables
		    ticks += sleepTime;
		    currTotElapsedTime = timer.getClockTicks();
		    elapsedTime = currTotElapsedTime - totalElapsedTime;
		    ++frameCount;
		    totalElapsedTime = currTotElapsedTime;
		}
		/*
		 * Thread t; // setup framerate stuff frameStart = 0; frameCount = 0;
		 * elapsedTime = 0; totalElapsedTime = 0; // get the current thread t =
		 * Thread.currentThread();
		 * 
		 * while (t == animation) { ticks++; // save start time frameStart =
		 * System.nanoTime(); // paint the frame repaint(); mouseClicked =
		 * false; // see how long it took to paint that frame elapsedTime =
		 * System.nanoTime() - frameStart; // ok, now sync to framerate try { //
		 * make sure framerate milliseconds have passed if (elapsedTime <
		 * framerate) { Thread.sleep((int) (framerate - elapsedTime)); } else { //
		 * don't starve Java's Garbage eater Thread.sleep(5); } }
		 * 
		 * catch (InterruptedException e) { break; } //
		 * System.out.println("Ticks = " + ticks); // System.out.println("TTime = " +
		 * elapsedTime); // update actual reported framerate ++frameCount;
		 * totalElapsedTime += (System.nanoTime() - frameStart); if
		 * (totalElapsedTime > 1000) { reportedFramerate = ((double) frameCount /
		 * (double) totalElapsedTime * 1000.0); // show the framerate
		 * showStatus("fps: " + reportedFramerate); // System.out.println("FPS: " +
		 * reportedFramerate);
		 * 
		 * frameCount = 0; totalElapsedTime = 0; } } // while animation
		 */

    } // run

    /**
     * Run game.
     */
    protected void runGame() {

    }

    /**
     * Sets the sleeptime check value.
     * 
     * @param amtFramesBetweenSleeptimeChecks the new sleeptime check value
     */
    public void setSleeptimeCheckValue(int amtFramesBetweenSleeptimeChecks) {
		framesBetweenSleepTimeCheck = amtFramesBetweenSleeptimeChecks;
    }

    /**
     * Do game setup here.
     */
    protected void setupGame() {
		gameletStarted = true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.applet.Applet#showStatus(java.lang.String)
     */
    public void showStatus(String strings) {
		System.out.println(strings);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.applet.Applet#start()
     */
    public void start() {
	// start animation thread
		animation.start();

    } // start

    /*
     * (non-Javadoc)
     * 
     * @see java.applet.Applet#stop()
     */
    public void stop() {
		animation = null;

    } // stop

    /**
     * Returns the elapsedTime or time taken to paint frame in clock ticks,
     * multiplied by 1000 to return milliseconds.
     * 
     * @return Again, this simply returns the time it took to paint the last
     * frame, in milliseconds.
     */
    public long timeToPaintLastFrame() {
		return elapsedTime * 1000;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.Container#update(java.awt.Graphics)
     */
    public void update(Graphics g) {
		paint(g);
    } 
    
    public void pause() {
    	gameletStarted = false;
    }
    
    public void endPause(){
    	gameletStarted = true;
    }

	public void setScene(QuadScene scene){
		sceneManager = scene;
	}
	public QuadScene getScene(){
		return sceneManager;
	}
}

// fixed bug in init(); framerate was set as 1000/desiredValue, this caused
// problem in run when the framerate was setup

// added EntityManager directory and also a registerEntity method for auto addition to the 
// EntityManager.

// adding MessageDispatching to the system.